/*************************************************************************
* Copyright (c) 2006-2008 Innobox Systems Private Limited
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in the
*    documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of its contributors
*    may not be used to endorse or promote products derived from
*    this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**************************************************************************
**************************************************************************
**** Please inform any bugs or improvements to opensource@innobox.com ****
**************************************************************************
*************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>              /* for open() */
#ifdef __linux__
#define HAVE_SYS_MMAN_H 1
#endif

#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>           /* for mmap() */
#endif
#include <sys/stat.h>           /* for stat() */
#include <errno.h>
#include <unistd.h>

#define IBX_CHANGES 1
#include "elfstructs.h"

#undef BIG_ENDIAN
#define BIG_ENDIAN 1
#define MAX_FILEPATH    128

/* Function prototypes for byte ordering */
extern Elf32_Word eword_swap (Elf32_Word word);
extern Elf32_Half ehalf_swap (Elf32_Half half);
void print_bytes (char *buf, int len, char *msg);

/* function prototypes */
static int valid_elf (char *image);
static void load_names (Elf32_Ehdr * header, char *image);

/* global sections name table */
char *name_table = NULL;
/* global name table for the symbol table */
char *sym_name_table = NULL;

unsigned char byte_order;

static void ibxSymDump (char **argv);
static void ibxSymSkip (char *sym);
static void ibxSymBuild (Elf32_Ehdr * header, char *image);

int
main (int argc, char *argv[])
{
  Elf32_Ehdr *elf_head;         /* ELF header structure */
  char elf_path[MAX_FILEPATH];  /* path for the file */
  int elf_fp;                   /* fd for the file */
  struct stat elf_stat;         /* used with stat() */
  char *elf_image;              /* used with mmap() */

  if (argc <= 3)
  {
    fprintf (stderr, "usage: %s <executable> <outfile> <include> [ignore]\r\n", argv[0]);
    exit (0);
  }

  while (argc > 4)
  {
    ibxSymSkip (argv[--argc]);
  }

  /*
   * get the filename and attempt to open it
   */
  strncpy (elf_path, argv[1], MAX_FILEPATH);

  if ((elf_fp = open (elf_path, O_RDONLY)) < 0)
  {
    fprintf (stderr, "Failed opening %s! %s\n", elf_path, strerror (errno));
    exit (1);
  }

  /*
   * use stat() to get the file size
   */
  if (stat (elf_path, &elf_stat) < 0)
  {
    fprintf (stderr, "Failed determining file size! %s\n", strerror (errno));
    exit (1);
  }

#if HAVE_SYS_MMAN_H
  /*
   * attempt to mmap() the file to an address
   */
  if ((elf_image = mmap (0, elf_stat.st_size, PROT_READ, MAP_PRIVATE, elf_fp, 0)) == MAP_FAILED)
  {
    fprintf (stderr, "Failed mapping ELF file to memory! %s\n", strerror (errno));
    exit (1);
  }
#else
  elf_image = calloc (1, elf_stat.st_size);
  read (elf_fp, elf_image, elf_stat.st_size);
#endif

  /*
   * check it's a valid ELF file, close and exit if not
   */
  if (!valid_elf (elf_image))
  {
    fprintf (stderr, "%s is not a valid ELF file!\n", elf_path);
    exit (1);
  }

  /*
   * fill the header structure
   */
  elf_head = (Elf32_Ehdr *) elf_image;
  byte_order = elf_head->e_ident[EI_DATA];

  /*
   * load the string table pointed out by the segment header
   */
  load_names (elf_head, elf_image);

  ibxSymBuild (elf_head, elf_image);

#if HAVE_SYS_MMAN_H
  if (munmap (elf_image, elf_stat.st_size) < 0)
  {
    fprintf (stderr, "Failed unmapping ELF file!\n");
    fprintf (stderr, "%s\n", strerror (errno));
    exit (1);
  }
#else
  free (elf_image);
#endif

  ibxSymDump (argv);
  return 0;
}

/* Function to swap the byte-order for Elf32_Half items (ushorts) */
Elf32_Half
ehalf_swap (Elf32_Half half)
{
  unsigned char ha[2];
#ifdef BIG_ENDIAN
  if (byte_order != ELFDATA2MSB)
  {
    return half;
  }
#else
  if (byte_order != ELFDATA2LSB)
  {
    return half;
  }
#endif

  ha[0] = (half & 0xff00) >> 8;
  ha[1] = (half & 0x00ff);
  return *(Elf32_Half *) ha;
}

/* Function to swap the byte-order for Elf32_Word items (ints) */
Elf32_Word
eword_swap (Elf32_Word word)
{
  unsigned char wa[4];

#ifdef BIG_ENDIAN
  if (byte_order != ELFDATA2MSB)
  {
    return word;
  }
#else
  if (byte_order != ELFDATA2LSB)
  {
    return word;
  }
#endif

  wa[0] = (word & 0xff000000) >> 24;
  wa[1] = (word & 0x00ff0000) >> 16;
  wa[2] = (word & 0x0000ff00) >> 8;
  wa[3] = (word & 0x000000ff);

  return *(Elf32_Word *) wa;
}

/* Loads the string table section pointed to by the ELF header's
 * e_shstrndx field and also loads the symbol table's string table */
static void
load_names (Elf32_Ehdr * header, char *image)
{
  Elf32_Shdr *ptr;
  int i;

  /* load the section names string table */
  ptr = (Elf32_Shdr *) (image + eword_swap (header->e_shoff));

  for (i = 0; i < ehalf_swap (header->e_shstrndx); i++)
  {
    ptr++;
  }
  name_table = (char *) (image + eword_swap (ptr->sh_offset));

  /* load the general string table */
  ptr = (Elf32_Shdr *) (image + eword_swap (header->e_shoff));
  for (i = 0; i < (int) ehalf_swap (header->e_shnum); i++)
  {
    if (strncmp ((name_table + eword_swap (ptr->sh_name)), ".strtab", 7) == 0)
    {
      sym_name_table = (char *) (image + eword_swap (ptr->sh_offset));
      break;
    }
    else
    {
      ptr++;
    }
  }

  if (sym_name_table == NULL)
  {
    fprintf (stdout, "\nNo symbol table defined!\n\n");
  }
}

/* Validates that the file is actually an ELF file
 * by checking the first four bytes. Returns zero if it isn't
 * an ELF file, non-zero if it is */
static int
valid_elf (char *image)
{
  unsigned char *magic;         /* hold the magic number */

  magic = (unsigned char *) image;      /* point to beginning of ELF image */

  if ((magic[0] == 0x7F) && (strncmp ((char *) (magic + 1), "ELF", 3) == 0))
  {
    return 1;
  }
  return 0;
}

void
print_bytes (char *buf, int len, char *msg)
{
  int i, max = 16;

  for (i = 0; i < len; i++)
  {
    if (i % max == 0)
    {
      printf ("\n%s %03d: ", msg, i);
    }
    printf ("%02X ", buf[i] & 0xFF);
  }
  printf ("\n\n");
}

typedef struct ibxSymSkip_s
{
  struct ibxSymSkip_s *next;
  char *name;
} ibxSymSkip_t;
static ibxSymSkip_t *ibxSymSkipHead = NULL;
static ibxSymSkip_t *ibxSymSkipTail = NULL;

static void
ibxSymSkip (char *name)
{
  ibxSymSkip_t *ent = calloc (1, sizeof (ibxSymSkip_t));
  ent->name = strdup (name);
  if (ibxSymSkipHead == NULL)
  {
    ibxSymSkipHead = ent;
  }
  else
  {
    ibxSymSkipTail->next = ent;
  }
  ibxSymSkipTail = ent;
}

static int
ibxSymIsValid (char *name)
{
  ibxSymSkip_t *ent;
  if (strchr (name, '@'))
  {
    return 0;
  }

  if (name[0] == '_')
  {
    return 0;
  }

  for (ent = ibxSymSkipHead; ent; ent = ent->next)
  {
    if (!strcmp (ent->name, name))
    {
      return 0;
    }
  }
  return 1;
}

typedef struct ibxSymEnt_s
{
  struct ibxSymEnt_s *prev;
  struct ibxSymEnt_s *next;
  char *name;
  long addr;
} ibxSymEnt_t;
static ibxSymEnt_t *ibxSymEntHead = NULL;
static ibxSymEnt_t *ibxSymEntTail = NULL;

static void
ibxSymRecord (char *name, long addr)
{
  ibxSymEnt_t *cur, *ent = calloc (1, sizeof (ibxSymEnt_t));

  ent->name = strdup (name);
  ent->addr = addr;
  ent->prev = ent->next = NULL;

  if (ibxSymEntHead == NULL)
  {
    /* new head tail */
    ibxSymEntHead = ibxSymEntTail = ent;
    return;
  }

  for (cur = ibxSymEntHead; cur; cur = cur->next)
  {
    if (addr < cur->addr)
    {
      if (cur->prev == NULL)
      {
        ibxSymEntHead = ent;    /* new head */
      }
      else
      {
        cur->prev->next = ent;
        ent->prev = cur->prev;
      }
      ent->next = cur;
      cur->prev = ent;
      return;
    }
  }

  /* new tail */
  ibxSymEntTail->next = ent;
  ent->prev = ibxSymEntTail;
  ibxSymEntTail = ent;
}

static char *license = "/*\n\
  * This file is generated using ibxSymGen utility\n\
  * which is part of ibxDevScripts at\n\
  * http://code.google.com/p/ibxdevscripts/\n\
  */\n";

static void
ibxSymDump (char **argv)
{
  ibxSymEnt_t *cur;
  FILE *out_fp;
  char fname[1024];

  sprintf (fname, "%s_extern.h", argv[2]);
  out_fp = fopen (fname, "w");
  fprintf (out_fp, license);
  fprintf (out_fp, "#ifndef IBX_SYMTBL_EXTERN_FILE_H\n");
  fprintf (out_fp, "#define IBX_SYMTBL_EXTERN_FILE_H\n\n");
  fprintf (out_fp, "#include \"%s\"\n\n", argv[3]);
  fprintf (out_fp, "#ifndef IBX_SYMTBL_EXTERN_START\n");
  fprintf (out_fp, "#define IBX_SYMTBL_EXTERN_START()\n");
  fprintf (out_fp, "#endif\n\n");
  fprintf (out_fp, "#ifndef IBX_SYMTBL_EXTERN\n");
  fprintf (out_fp, "#define IBX_SYMTBL_EXTERN(x,y,z)\n");
  fprintf (out_fp, "#endif\n\n");
  fprintf (out_fp, "#ifndef IBX_SYMTBL_EXTERN_END\n");
  fprintf (out_fp, "#define IBX_SYMTBL_EXTERN_END()\n");
  fprintf (out_fp, "#endif\n\n");
  fprintf (out_fp, "\n\nIBX_SYMTBL_EXTERN_START()\n");
  for (cur = ibxSymEntHead; cur; cur = cur->next)
  {
    fprintf (out_fp, "IBX_SYMTBL_EXTERN(\"%s\", %s, 0x%08X)\n", cur->name, cur->name, (unsigned int) cur->addr);
  }
  fprintf (out_fp, "\n\nIBX_SYMTBL_EXTERN_END()\n\n");
  fprintf (out_fp, "#endif\n\n");
  fclose (out_fp);

  sprintf (fname, "%s_entry.c", argv[2]);
  out_fp = fopen (fname, "w");
  fprintf (out_fp, license);
  fprintf (out_fp, "#include \"%s_extern.h\"\n\n", argv[2]);
  fprintf (out_fp, "#ifndef IBX_SYMTBL_ENTRY_START\n");
  fprintf (out_fp, "typedef struct {char *name; long addr;} ibxSymTblEnt_t;\n");
  fprintf (out_fp, "#define IBX_SYMTBL_ENTRY_START() ibxSymTblEnt_t ibxSymTble [] = {\n");
  fprintf (out_fp, "#endif\n\n");
  fprintf (out_fp, "#ifndef IBX_SYMTBL_ENTRY\n");
  fprintf (out_fp, "#define IBX_SYMTBL_ENTRY(x,y,z) {x,z},\n");
  fprintf (out_fp, "#endif\n\n");
  fprintf (out_fp, "#ifndef IBX_SYMTBL_ENTRY_END\n");
  fprintf (out_fp, "#define IBX_SYMTBL_ENTRY_END() };\n");
  fprintf (out_fp, "#endif\n\n");
  fprintf (out_fp, "\n\nIBX_SYMTBL_ENTRY_START()\n");
  for (cur = ibxSymEntHead; cur; cur = cur->next)
  {
    fprintf (out_fp, "IBX_SYMTBL_ENTRY(\"%s\", %s, 0x%08X)\n", cur->name, cur->name, (unsigned int) cur->addr);
  }
  fprintf (out_fp, "\n\nIBX_SYMTBL_ENTRY_END()\n");
  fclose (out_fp);

  sprintf (fname, "%s_init.c", argv[2]);
  out_fp = fopen (fname, "w");
  fprintf (out_fp, license);
  fprintf (out_fp, "#include \"%s_extern.h\"\n\n", argv[2]);
  fprintf (out_fp, "#ifndef IBX_SYMTBL_INIT_START\n");
  fprintf (out_fp, "#define IBX_SYMTBL_INIT_START()\n");
  fprintf (out_fp, "#endif\n\n");
  fprintf (out_fp, "#ifndef IBX_SYMTBL_INIT\n");
  fprintf (out_fp, "#define IBX_SYMTBL_INIT(x,y,z)\n");
  fprintf (out_fp, "#endif\n\n");
  fprintf (out_fp, "#ifndef IBX_SYMTBL_INIT_END\n");
  fprintf (out_fp, "#define IBX_SYMTBL_INIT_END()\n");
  fprintf (out_fp, "#endif\n\n");
  fprintf (out_fp, "\n\nIBX_SYMTBL_INIT_START()\n");
  for (cur = ibxSymEntHead; cur; cur = cur->next)
  {
    fprintf (out_fp, "IBX_SYMTBL_INIT(\"%s\", %s, 0x%08X)\n", cur->name, cur->name, (unsigned int) cur->addr);
  }
  fprintf (out_fp, "\n\nIBX_SYMTBL_INIT_END()\n");
  fclose (out_fp);
}

static void
ibxSymBuild (Elf32_Ehdr * header, char *image)
{
  Elf32_Shdr *sectab;           /* section table offset */
  Elf32_Half sectab_size;       /* section table size */
  int num_syms;                 /* number of symbols */
  int i = 0;
  Elf32_Shdr *ptr;              /* segment table buffer */
  Elf32_Shdr *sym_tab;          /* current symbol */
  Elf32_Sym *symbol;

  /* if no string table can be found for the symbols then die */
  if (sym_name_table == NULL)
  {
    fprintf (stdout, "Symbol table not found, possibly a stripped" " binary.\n");
    return;
  }

  /* find the symbol table section */
  sectab = (Elf32_Shdr *) (image + eword_swap (header->e_shoff));
  sectab_size = ehalf_swap (header->e_shnum) * ehalf_swap (header->e_shentsize);

  ptr = (Elf32_Shdr *) sectab;
  while (eword_swap (ptr->sh_type) != SHT_SYMTAB)
  {
    ptr++;
  }

  num_syms = eword_swap (ptr->sh_size) / eword_swap (ptr->sh_entsize);
  sym_tab = (Elf32_Shdr *) (image + eword_swap (ptr->sh_offset));
  symbol = (Elf32_Sym *) sym_tab;

  /* now process all of the symbols, showing function items only */
  for (i = 0, symbol = (Elf32_Sym *) sym_tab; i < num_syms; i++)
  {
    if (ELF32_ST_TYPE (symbol->st_info) == STT_FUNC)
    {
      if ((ELF32_ST_BIND (symbol->st_info) == STB_GLOBAL) || (ELF32_ST_BIND (symbol->st_info) == STB_LOCAL))
      {
        /* Display the function information */
        char *ibxSymName = (sym_name_table + eword_swap (symbol->st_name));
        if (ibxSymIsValid (ibxSymName))
        {
          ibxSymRecord (ibxSymName, eword_swap (symbol->st_value));
        }
      }
    }
    symbol++;
  }
}
